'use strict';

function setupModuleLoader(){
    var ensure = function(obj, name, factory){
        return obj[name] || (obj[name] = factory()); //load once pattern
    };
    var angular = ensure(window, 'angular', Object);

    var createModule = function(moduleName, moduleDepedencies, modules, configFn){
        if (moduleName === 'hasOwnProperty'){
            throw 'hasOwnProperty is not a valid module name';
        }
        var invokeQueue = [];
        var configBlocks = [];

        var invokeLater = function(service, method, arrayMethod, queue){
            //1) The object whose method to call, 2) The name of the method to call, and 3) The method arguments:
            return function(){
                queue = queue || invokeQueue;
                queue[arrayMethod || 'push']([service, method, arguments]);
                return moduleInstance;
            };
        };

        var moduleInstance = {
            name: moduleName,
            requires: moduleDepedencies,
            constant: invokeLater('$provide', 'constant', 'unshift'),
            provider: invokeLater('$provide', 'provider'),
            factory: invokeLater('$provide', 'factory'),
            value: invokeLater('$provide', 'value'),
            service: invokeLater('$provide', 'service'),
            decorator: invokeLater('$provide', 'decorator'),
            filter: invokeLater('$filterProvider', 'register'),
            directive: invokeLater('$compileProvider', 'directive'),
            controller: invokeLater('$controllerProvider', 'register'),
            config: invokeLater('$injector', 'invoke', 'push', configBlocks),
            run: function(fn){
                moduleInstance._runBlocks.push(fn);
                return moduleInstance;
            },
            _invokeQueue: invokeQueue,
            _configBlocks: configBlocks,
            _runBlocks: []

        };
        if (configFn){
            moduleInstance.config(configFn);
        }
        modules[moduleName] = moduleInstance;
        return moduleInstance;
    };
    var getModule = function(name, modules){
        if (modules.hasOwnProperty(name)){
            return modules[name];
        }else{
            throw 'Module ' + name + ' is not available!';
        }
    };
    var module = ensure(angular, 'module', function(){
        var modules = {};
        return function(name, requires, configFn){
            if (requires){
                return createModule(name, requires, modules, configFn);
            }else{
                return getModule(name, modules);
            }
        };
    });
}

module.exports = setupModuleLoader;